package org.gwt.grin.rebind.util;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.gwt.grin.client.util.Rectangle;
import org.gwt.grin.rebind.SECommand;
import org.gwt.grin.rebind.input.SEVisualRCHandler;

/**
 * A helper class for creating a {@link VisualRCHandler} object.
 * <p>
 * To use this, create an instance, populate it, then call getFinishedHandler().
 * 
 * @author Bill Foote (http://jovial.com)
 * @author ggeorg
 */
public class VisualRCHandlerHelper {

	private String handlerName;
	private boolean startSelected = false;
	private ArrayList<String> gridAlternateNames = new ArrayList<String>();
	private ArrayList<ArrayList<ArrayList<VisualRCHandlerCell>>> grids = new ArrayList<ArrayList<ArrayList<VisualRCHandlerCell>>>();
	private Map<String, Integer> states = new HashMap<String, Integer>();
	// Maps state name to number, counting from 0
	private ArrayList<Map<String, VisualRCHandlerCell>> stateToCell = new ArrayList<Map<String, VisualRCHandlerCell>>();
	// Maps state name to the defining cell for each grid
	private ArrayList<Map<String, String>> rcOverrides = new ArrayList<Map<String, String>>();
	// See addRCOverrides
	private SECommand[][] selectCommands;
	private SECommand[][] activateCommands;
	private Rectangle[] mouseRects = null;
	private int[] mouseRectStates = null;
	private int timeout;
	private SECommand[] timeoutCommands;

	public VisualRCHandlerHelper() {
	}

	/**
	 * Sets the handler's name
	 **/
	public void setHandlerName(String name) {
		handlerName = name;
	}

	/**
	 * Sets the startSelected flag
	 **/
	public void setStartSelected(boolean b) {
		startSelected = b;
	}

	/**
	 * Adds an RC grid.
	 * 
	 * @return null if all goes well, or an error message if there's a problem.
	 **/
	public String addGrid(ArrayList<ArrayList<VisualRCHandlerCell>> grid) {
		this.grids.add(grid);
		if (grid.size() == 0) {
			return "Empty grid";
		}

		int columns = grid.get(0).size();
		if (columns == 0) {
			return "Empty grid";
		}
		for (int y = 1; y < grid.size(); y++) {
			if (grid.get(y).size() != columns) {
				return "Grid row " + y
						+ " (counting from 0) has a different length.";
			}
		}

		// Set up the cells with us (the handler) and the x,y pos
		for (int y = 0; y < grid.size(); y++) {
			List<VisualRCHandlerCell> row = grid.get(y);
			for (int x = 0; x < row.size(); x++) {
				VisualRCHandlerCell cell = row.get(x);
				int alternate = this.grids.size() - 1;
				cell.setHelper(this, alternate);
				cell.setXY(x, y);
			}
		}

		// For each cell, populate the states map

		Map<String, VisualRCHandlerCell> newStateToCell = new HashMap<String, VisualRCHandlerCell>();
		stateToCell.add(newStateToCell);
		HashSet<String> dupCheck = new HashSet<String>();
		for (int y = 0; y < grid.size(); y++) {
			for (int x = 0; x < columns; x++) {
				VisualRCHandlerCell cell = grid.get(y).get(x);
				String msg = cell.addState(states, dupCheck, newStateToCell);
				if (msg != null) {
					return msg;
				}
			}
		}

		// Make sure that a cell doesn't
		// refer to a cell that itself refers to a cell.

		for (int y = 0; y < grid.size(); y++) {
			for (int x = 0; x < columns; x++) {
				VisualRCHandlerCell cell = grid.get(y).get(x);
				VisualRCHandlerCell to = cell.getRefersTo();
				if (to != null && to.getRefersTo() != null) {
					return "Grid refers to cell that refers to cell at x,y "
							+ x + ", " + y + " (counting from 0)";
				}
			}
		}

		// Now check all the cells
		for (int y = 0; y < grid.size(); y++) {
			for (int x = 0; x < columns; x++) {
				VisualRCHandlerCell cell = grid.get(y).get(x);
				String msg = cell.check();
				if (msg != null) {
					return msg;
				}
			}
		}

		return null; // null means "no error to report"
	}

	public void addGridAlternateName(String name) {
		gridAlternateNames.add(name);
	}

	public ArrayList<ArrayList<VisualRCHandlerCell>> getGrid(int i) {
		return grids.get(i);
	}

	/**
	 * Sets the RC override list. The maps Maps a key of the form
	 * "direction:from state name" to a state name. direction is "up", "down",
	 * "right" or "left". When in the state is "from state name", the given
	 * direction will move to the second state.
	 * <p>
	 * The value of the map is the state to go to, or the special string
	 * "&lt;activate>".
	 **/
	public void addRCOverrides(Map<String, String> rcOverrides) {
		this.rcOverrides.add(rcOverrides);
	}

	public Map<String, String> getRCOverrides(int i) {
		return rcOverrides.get(i);
	}

	public void setSelectCommands(SECommand[][] commands) {
		selectCommands = commands;
	}

	public void setActivateCommands(SECommand[][] commands) {
		activateCommands = commands;
	}

	public void setMouseRects(Rectangle[] rects) {
		mouseRects = rects;
	}

	public void setMouseRectStates(int[] states) {
		mouseRectStates = states;
	}

	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}

	public void setTimeoutCommands(SECommand[] commands) {
		timeoutCommands = commands;
	}

	/**
	 * @return The map from state name to index in array of states
	 **/
	public Map<String, Integer> getStates() {
		return states;
	}

	Map<String, VisualRCHandlerCell> getStateToCell(int i) {
		return stateToCell.get(i);
	}

	/**
	 * @return The state number referred to by the given cell, or -1 if that
	 *         cell isn't a state or doesn't refer to one.
	 **/
	public int getState(int alternate, int column, int row) {
		String name = grids.get(alternate).get(row).get(column).getState();
		if (name == null) {
			return -1;
		}
		return states.get(name).intValue();
	}

	/**
	 * @throws IOException
	 *             if there's an inconsistency in the handler
	 **/
	public SEVisualRCHandler getFinishedHandler() throws IOException {
		int[][] upDownAlternates = new int[grids.size()][];
		int[][] rightLeftAlternates = new int[grids.size()][];
		String[] gridNames = gridAlternateNames
				.toArray(new String[gridAlternateNames.size()]);
		String[] stateNames = new String[states.size()];
		for (Map.Entry<String, Integer> entry : states.entrySet()) {
			int stateNum = entry.getValue().intValue();
			stateNames[stateNum] = entry.getKey();
		}
		for (int i = 0; i < grids.size(); i++) {
			int[] upDown = new int[states.size()];
			upDownAlternates[i] = upDown;
			int[] rightLeft = new int[states.size()];
			rightLeftAlternates[i] = rightLeft;
			for (Map.Entry<String, Integer> entry : states.entrySet()) {
				int stateNum = entry.getValue().intValue();
				VisualRCHandlerCell cell = stateToCell.get(i).get(
						entry.getKey());
				if (cell != null) {
					upDown[stateNum] = cell.getUpDown();
					rightLeft[stateNum] = cell.getRightLeft();
				}
			}
		}
		SEVisualRCHandler result = new SEVisualRCHandler(handlerName,
				gridNames, stateNames, upDownAlternates, rightLeftAlternates,
				selectCommands, activateCommands, mouseRects, mouseRectStates,
				timeout, timeoutCommands, startSelected);
		return result;
	}
}
